package com.boot.security.service.authentication;

import com.boot.core.exception.UnsupportedClassException;
import com.boot.security.model.bo.LoginAccountAuthentication;
import com.boot.security.model.bo.TokenAuthentication;
import com.boot.security.model.dto.AccountGroup;
import com.boot.security.model.dto.LoginAccount;
import com.boot.security.model.vo.AccountInfo;
import com.boot.security.model.vo.WebDetail;
import com.boot.web.http.HttpModel;
import com.boot.web.util.HttpResponseUtil;
import com.boot.web.util.IpUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import java.io.IOException;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author 霜寒 <1621856595@qq.com>
 * @date 2021/1/18 19:23
 **/
@Slf4j
public class LoginFilter extends AbstractAuthenticationProcessingFilter {

    private final ObjectMapper om;
    private final Validator validator;

    public LoginFilter(RequestMatcher requiresAuthenticationRequestMatcher,
                       AuthenticationManager authenticationManager,
                       AuthenticationSuccessHandler successHandler,
                       AuthenticationFailureHandler failureHandler,
                       ObjectMapper om,
                       Validator validator) {
        super(requiresAuthenticationRequestMatcher, authenticationManager);
        setAuthenticationSuccessHandler(successHandler);
        setAuthenticationFailureHandler(failureHandler);
        this.om = om;
        this.validator = validator;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        log.info("======================================== LoginFilter =======================================");
        int len = request.getContentLength();
        ServletInputStream inputStream = request.getInputStream();
        byte[] buffer = new byte[len];
        inputStream.read(buffer, 0, len);
        LoginAccount loginAccount = om.readValue(buffer, LoginAccount.class);
        if (loginAccount == null) {
            throw new UsernameNotFoundException("Account is not found.");
        }

        Set<ConstraintViolation<LoginAccount>> violations;
        switch (loginAccount.getType()) {
            case LoginAccount.USERNAME_PASSWORD:
                violations = validator.validate(loginAccount, AccountGroup.UserNameGroup.class);
                break;
            case LoginAccount.PHONE_PASSWORD:
                violations = validator.validate(loginAccount, AccountGroup.PhoneGroup.class);
                break;
            case LoginAccount.EMAIL_PASSWORD:
                violations = validator.validate(loginAccount, AccountGroup.EmailGroup.class);
                break;
            default:
                throw new AuthenticationServiceException("登录方式错误");
        }

        if (violations.size() > 0) {
            throw new AuthenticationServiceException(violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toList()).toString());
        }

        Authentication authentication = new LoginAccountAuthentication(loginAccount,
                WebDetail.builder().ip(IpUtil.getHttpRequestHostAddress(request)).rememberMe(loginAccount.getRememberMe()).build());

        return this.getAuthenticationManager().authenticate(authentication);
    }

    /*
        字母、数字、_、@、# 允许通过
     */
    @SuppressWarnings("all")
    private static final String SPECIAL_CHAR = "[ `~!$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]|\n|\r|\t";

    @Deprecated
    private boolean findSpecialChar(String s) {
        Pattern pattern = Pattern.compile(SPECIAL_CHAR);
        Matcher matcher = pattern.matcher(s);
        return matcher.find();
    }

    @Slf4j
    public static class LoginSuccessHandler implements AuthenticationSuccessHandler {

        private final TokenService tokenService;

        public LoginSuccessHandler(TokenService tokenService) {
            this.tokenService = tokenService;
        }

        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            if (authentication instanceof TokenAuthentication) {
                AccountInfo accountInfo = ((TokenAuthentication) authentication).getAccountInfo();
                String token = tokenService.build(accountInfo);

                log.info("用户 {} 登录成功", authentication.getName());
                HttpResponseUtil.writeResponse(response, ResponseEntity.status(HttpStatus.OK)
                        .header(TokenService.TOKEN, token)
                        .body(HttpModel.<AccountInfo>builder().data(accountInfo).build()));
            } else {
                throw new UnsupportedClassException(this.getClass(), authentication.getClass());
            }
        }
    }

    @Slf4j
    public static class LoginFailureHandler implements AuthenticationFailureHandler {
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
            log.warn("未知用户试图登录,错误信息：{}", exception.getMessage());
            HttpResponseUtil.writeResponse(response, HttpResponseUtil.unAuthorized(exception.getMessage()));
        }
    }
}
